﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Site_Extractor;
using MIL.Html;
using System.Xml.Serialization;
using System.Text.RegularExpressions;

namespace Common
{
	[XmlInclude(typeof(Rule))]
	[XmlRoot("ForumRule", IsNullable = false, Namespace = "")]
	public class ForumRule
	{
		public RuleAction ruleAction;
		public object[] actionParams;
		public ForumRule nextRule;

		public ForumRule() { }

		public static object PerformRuleAction(RuleAction ruleAction, params object[] Params)
		{
			try
			{
				object ret = null;
				switch (ruleAction)
				{
					case RuleAction.DownloadPage:
						{
							var url = Params[0] as string;
                            ret = HttpHelper.HttpDownloader.DownloadPage(new Uri(url));
						} break;
					case RuleAction.GetNodes:
						{
							var code = Params[0] as string;
							ret = HtmlDownloader.GetNodes(code);
						} break;
					case RuleAction.GetFromString:
						{
							var source = Params[0] as string;
							var start = Params[1] as string;
							var end = Params[2] as string;
							ret = ForumHandler.GetFromString(source, start, end);
						} break;
					case RuleAction.GetTextByRule:
						{
							var nodes = Params[0] as HtmlNodeCollection;
							var rule = Params[1] as Rule;
							var result = SiteExtractor.GetTextByRule(nodes, rule);
							ret = result;
						} break;
					case RuleAction.GetNodeByRule:
						{
							var nodes = Params[0] as HtmlNodeCollection;
							var nodesRule = Params[1] as Rule;
							ret = SiteExtractor.GetNodeByRule(nodes, nodesRule);
						} break;
					case RuleAction.GetDate:
						{
							var dateString = Params[0] as string;
							var dateFormat = Params[1] as string;
							DateTime date;
							ForumHandler.GetDate(dateString, dateFormat, out date);
							ret = date;
						} break;
					case RuleAction.AddToString:
						{
							var input = Params[0] as string;
							var addition = Params[1] as string;
							string output = input + addition;
							ret = output;
						} break;
					case RuleAction.AddStringTo:
						{
							var input = Params[0] as string;
							var addition = Params[1] as string;
							string output = addition+input;
							ret = output;
						} break;
					case RuleAction.ToLower:
						{
							var input = Params[0] as string;
							var output = input.ToLower();
							ret = output;
						} break;
					case RuleAction.Contains:
						{
							var input = Params[0] as string;
							var condition = Params[1] as string;
							var output = input.Contains(condition);
							ret = output;
						} break;
					case RuleAction.NotContains:
						{
							var input = Params[0] as string;
							var condition = Params[1] as string;
							var output = !input.Contains(condition);
							ret = output;
						} break;
					case RuleAction.ContainsCharCount:
						{
							var input = Params[0] as string;
							var c = (char)Params[1];
							var toCount = (int)Params[2];
							var count = 0;
							foreach (char ch in input) if (ch == c) count++;
							ret = (count == toCount);
						} break;
					case RuleAction.Strip:
						{
							var input = Params[0]as string;
							var output = HtmlStripper.StripFromHtml(input);
							ret = output;
						} break;
					case RuleAction.Replace:
						{
							var input = Params[0] as string;
							var original = Params[1] as string;
							var replacement = Params[2] as string;
							var output = input.Replace(original, replacement);
							ret = output;
						} break;
					case RuleAction.RemoveChars:
						{
							var input = Params[0] as string;
							var toRemove = new List<char>();
							foreach(object o in Params) if (o is char) toRemove.Add((char)o);
							var output=input;
							foreach (char c in toRemove) output = output.Replace(c.ToString(), string.Empty);
							ret = output;
						} break;
					case RuleAction.FromListString:
						{
							var collection = Params[0] as List<string>;
							var idx = (int)Params[1];
							ret = collection.ElementAt(idx);
						} break;
					case RuleAction.ForEach: 
						{
							var collection = Params[0] as ICollection<object>;
							var rule = (RuleAction)Params[1];
							var args = Params[2] as object[];
							var result = new List<object>();
							foreach (object o in collection)
							{
								result.Add(ForumRule.PerformRuleAction(rule, o, args));
							}
							ret = result;
						} break;
					case RuleAction.BoolAnd:
						{
							var input = Params[0];
							var r1 = Params[1] as ForumRule;
							var r2 = Params[2] as ForumRule;
							var b1 = RunRule(r1, input);
							if ((bool)b1)
							{
								var b2 = RunRule(r2, input);
								var result = (bool)b1 && (bool)b2;
								ret = result;
							}
							else ret = false;
						} break;
					case RuleAction.BoolOr:
						{
							var input = Params[0];
							var r1 = Params[1] as ForumRule;
							var r2 = Params[2] as ForumRule;
							var b1 = RunRule(r1, input);
							var b2 = RunRule(r2, input);
							var result = (bool)b1 || (bool)b2;
							ret = result;
						} break;
					case RuleAction.SubsringUntilFirst:
						{
							var input = Params[0] as string;
							var ch = (char)Params[1];
							var result = input.Substring(0,input.IndexOf(ch));
							ret = result;
						} break;
					case RuleAction.SubstringFromLast:
						{
							var input = Params[0] as string;
							var ch = (char)Params[1];
							var result = input.Substring(input.LastIndexOf(ch)+1);
							ret = result;
						} break;
					case RuleAction.GatherAllLinks:
						{
							var link = Params[0] as string;
							var rule = Params[1] as ForumRule;
							//var result = ForumHandler.GatherAllLinks(link, (s => (bool)RunRule(rule, s)));
							//ret = result;
						} break;
					case RuleAction.GatherAllLinksBad:
						{
							var link = Params[0] as string;
							var rule = Params[1] as ForumRule;
							//var result = ForumHandler.GatherAllLinks(link, (s => (bool)RunRule(rule, s)),true);
							//ret = result;
						} break;
					case RuleAction.GetTextFromNode:
						{
							var node = Params[0] as HtmlNode;
							var recursive = false;
							if (Params.Length == 2) recursive = (bool)Params[1];
							var result = ForumHandler.GetTextFromNode(node,recursive);
							ret = result;
						} break;
					case RuleAction.If: 
						{
							var input = Params[0];
							var rule = Params[1] as ForumRule;
							var trueRule = Params[2] as ForumRule;
							var falseRule = Params[3] as ForumRule;
							var ruleResult = (bool)RunRule(rule, input);
							object result;
							if (ruleResult) result = RunRule(trueRule, input);
							else result = RunRule(falseRule, input);
							ret = result;
						} break;
					case RuleAction.Split:
						{
							var input = Params[0] as string;
							var ch = (char)Params[1];
							var result = new List<string>(input.Split(ch));
							ret = result;
						} break;
					case RuleAction.RemoveFromString:
						{
							var input = Params[0] as string;
							var toRemove = new List<string>();
							foreach (object o in Params) if (o != Params[0]) toRemove.Add(o as string);
							var result = input;
							foreach (string s in toRemove) result = result.Replace(s, string.Empty);
							ret = result;
						} break;
					case RuleAction.CountIs:
						{
							var input = Params[0] as List<string>;
							var count = (int)Params[1];
							var result = input.Count == count;
							ret = result;
						} break;
					case RuleAction.ToInt:
						{
							var input = Params[0] as string;
							var result = int.Parse(input);
							ret = result;
						} break;
					case RuleAction.GreaterThan:
						{
							var input = (int)Params[0];
							var compareWith = (int)Params[1];
							var result = input > compareWith;
							ret = result;
						} break;
					case RuleAction.RxMatch:
						{
							var input = Params[0] as string;
							var rx = Params[1] as string;
							var matchCase = false;
							if (Params.Length == 3) matchCase = (bool)Params[2];
							var rxOpts = RegexOptions.None;
							if (!matchCase) rxOpts = RegexOptions.IgnoreCase;
							Regex regex = new Regex(rx, rxOpts);
							var result = regex.IsMatch(input);
							ret = result;
						} break;
					case RuleAction.GetRxMatches:
						{
							var input = Params[0] as string;
							var rx = Params[1] as string;
							string group=string.Empty;
							int grpNum=0;
							try { grpNum = (int)Params[2]; }
							catch { group = Params[2] as string; }
							var rxOpts = RegexOptions.Multiline|RegexOptions.IgnoreCase;
							Regex regex = new Regex(rx, rxOpts);
							var matches = regex.Matches(input);
							if (Params.Length == 3)
							{
								var result = new List<string>();
								foreach (Match m in matches)
								{
									string toAdd;
									if (group != string.Empty)
									{ toAdd = m.Groups[group].Value; }
									else { toAdd = m.Groups[grpNum].Value; }
									result.Add(toAdd);
								}
								ret = result;
							}
							else if (Params.Length == 4)
							{
								int idx=-1;
								try { idx = (int)Params[3]; }
								catch
								{
									var index = (Params[3] as string).ToLower();
									if (index == "first") idx = 0;
									else if (index == "last") { if (matches.Count == 0) idx = 0; else idx = matches.Count - 1; } 
								}
								string result;
								if (group != string.Empty)
									{ result = matches[idx].Groups[group].Value; }
									else { result = matches[idx].Groups[grpNum].Value;}
								ret = result;
							}
						} break;
					case RuleAction.GetMessageLinksRx:
						{
							var input = Params[0] as string;
							var rx = Params[1] as string;
							var dateFormat = Params[2] as string;
							var matches = Regex.Matches(input, rx, RegexOptions.IgnoreCase | RegexOptions.Multiline);
							var result = new List<ForumMessageInfo>();
							foreach (Match m in matches)
							{
								DateTime date;
								string link;
								try { ForumHandler.GetDate(m.Groups["date"].Value, dateFormat, out date); }
								catch { date = new DateTime(); }
								try { link = m.Groups["link"].Value; }
								catch { link = string.Empty; }
								result.Add(new ForumMessageInfo(date, link));
							}
							ret = result;
						} 
						break;

				}
				return ret;
			}
			catch (Exception ex) { return ex; }
		}
		public static object HandleForumRule(ForumRule rule)
		{
			object res;
			res = PerformRuleAction(rule.ruleAction, rule.actionParams);
			if (rule.nextRule != null)
			{
				var innerRule = rule.nextRule;
				List<object> objs = new List<object>();
				objs.Add(res);
				if (innerRule.actionParams != null) objs.AddRange(innerRule.actionParams);
				ForumRule tmpRule = new ForumRule();
				tmpRule.actionParams = objs.ToArray();
				tmpRule.nextRule = innerRule.nextRule;
				tmpRule.ruleAction = innerRule.ruleAction;
				res = HandleForumRule(tmpRule);
			}
			return res;
		}
		public static object RunRule(ForumRule rule, object arg)
		{
			ForumRule tmpRule = new ForumRule();
			List<object> args = new List<object>();
			args.Add(arg);
			if (rule.actionParams != null) args.AddRange(rule.actionParams);
			tmpRule.actionParams = args.ToArray();
			tmpRule.ruleAction = rule.ruleAction;
			tmpRule.nextRule = rule.nextRule;
			return ForumRule.HandleForumRule(tmpRule);
		}
		public override string ToString()
		{
			var p = string.Empty;
			if (actionParams != null && actionParams.Length > 0)
				p = string.Format("{0} parameter{1}", actionParams.Length, actionParams.Length > 0 ? "s" : string.Empty);
			else p = "0 parameters";

			return string.Format("{0} ({1})", ruleAction, p);
		}
	}
	[XmlRoot("RuleAction", IsNullable = false, Namespace = "")]
	public enum RuleAction 
	{ 
		None,
		/// <summary>
		/// Downloads a page
		/// input (string url, string encoding)
		/// output (string html)
		/// </summary>
		DownloadPage, 
		/// <summary>
		/// input (string htmlCode)
		/// output (HtmlNodeCollection nodes)
		/// </summary>
		GetNodes, 
		/// <summary>
		/// input (string source, string start, string end)
		/// output (List(string) substrings)
		/// </summary>
		GetFromString,
		/// <summary>
		/// input (HtmlNodeCollection nodes, Rule rule)
		/// output (string result)
		/// </summary>
		GetTextByRule, 
		/// <summary>
		/// input (HtmlNodeCollection nodes, Rule rule)
		/// output (HtmlNode node)
		/// </summary>
		GetNodeByRule, 
		/// <summary>
		/// input (string dateString, string dateFormat)
		/// output (DateTime date)
		/// </summary>
		GetDate, 
		/// <summary>
		/// input (string input, string addition)
		/// output (string input+addition)
		/// </summary>
		AddToString,
		/// <summary>
		/// input (string input, string addition)
		/// output (string addition+input)
		/// </summary>
		AddStringTo, 
		/// <summary>
		/// input (string input)
		/// output (string output)
		/// </summary>
		ToLower, 
		/// <summary>
		/// input (string input, string toCheck)
		/// output (bool result)
		/// </summary>
		Contains, 
		/// <summary>
		/// input (string input, string toCheck)
		/// output (bool result)
		/// </summary>
		NotContains,
		/// <summary>
		/// input (string input, char c, int count)
		/// output (bool result)
		/// </summary>
		ContainsCharCount, 
		/// <summary>
		/// input (string htmlCode)
		/// output (string strippedHtmlCode)
		/// </summary>
		Strip,
		/// <summary>
		/// input (string input, char[] toRemove)
		/// output (string output)
		/// </summary>
		RemoveChars,
		/// <summary>
		/// input (string input, string toFind, string toReplace)
		/// output (string output)
		/// </summary>
		Replace,
		/// <summary>
		/// input (Collection c, RuleAction act, {object[] parameters})
		/// output (List(object) result)
		/// </summary>
		ForEach,
		/// <summary>
		/// input (input for r1/r2 ,ForumRule r1, ForumRule r2)
		/// output (bool result of r1 AND r2)
		/// </summary>
		BoolAnd,
		/// <summary>
		/// input (input for r1/r2 ,ForumRule r1, ForumRule r2)
		/// output (bool result of r1 OR r2)
		/// </summary>
		BoolOr,
		/// <summary>
		/// input (List(string) list, int index)
		/// output (string result)
		/// </summary>
		FromListString,
		/// <summary>
		/// input (string source, char substringTillThisChar)
		/// output (string result)
		/// </summary>
		SubsringUntilFirst,
		/// <summary>
		/// input (string link, ForumRule check_links_with)
		/// output (list(string) links)
		/// </summary>
		GatherAllLinks,
		/// <summary>
		/// input (string link, ForumRule check_links_with)
		/// output (list(string) links [including bad chars])
		/// </summary>
		GatherAllLinksBad,
		/// <summary>
		/// input (HtmlNode node, [optional bool recursive])
		/// output (string text)
		/// </summary>
		GetTextFromNode,
		/// <summary>
		/// input (string source, char substringFromThisChar)
		/// output (string result)
		/// </summary>
		SubstringFromLast,
		/// <summary>
		/// input (object input, ForumRule ifRule, ForumRule trueRule, ForumRule falseRule)
		/// output (result of either trueRule or falseRule)
		/// </summary>
		If,
		/// <summary>
		/// input (string input, char splitChar)
		/// output (list(string) result)
		/// </summary>
		Split,
		/// <summary>
		/// input (string input, string[] toRemove)
		/// output (string output)
		/// </summary>
		RemoveFromString,
		/// <summary>
		/// input (list(string) input, int count)
		/// output (bool result)
		/// </summary>
		CountIs,
		/// <summary>
		/// input (string int)
		/// output (int result)
		/// </summary>
		ToInt,
		/// <summary>
		/// input (int number, int compareWith)
		/// output (bool (number > compareWith)
		/// </summary>
		GreaterThan,
		/// <summary>
		/// input (string input, string regularExpression, [optional bool matchCase])
		/// output (bool result)
		/// </summary>
		RxMatch,
		/// <summary>
		/// input (string input, string regularExpression, int groupNumber, [optional int matchNumber])
		/// output (list(string) OR [string match])
		/// </summary>
		GetRxMatches,
		/// <summary>
		/// input (string input, string regularExpression (containing groups: date, link), string dateFormat)
		/// output (list (ForumMessageLink)
		/// </summary>
		GetMessageLinksRx
	}
}
